<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>实现一个远古扫雷游戏</title>
    <link href="https://fonts.googleapis.com/css?family=Space+Mono:400,700" rel="stylesheet" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
    <style>
        @charset "UTF-8";

        *,
        *:before,
        *:after {
            box-sizing: inherit;
        }

        html {
            box-sizing: border-box;
        }

        body {
            text-align: center;
            color: #333;
            padding: 20px 20px 100px;
            background-color: #efefef;
            font-family: 'Space Mono', monospace;
        }

        h1 {
            margin: 0 0 32px;
        }

        p {
            font-size: 1em;
            line-height: 1.25em;
        }

        a {
            color: inherit;
        }

        img {
            height: auto;
            max-width: 100%;
        }

        .wrap {
            text-align: center;
            position: relative;
            display: inline-block;
        }

        .legend {
            font-size: 14px;
            margin: 0 auto 32px;
        }

        .legend h4,
        .legend p {
            margin: 0 0 6px;
        }

        .legend code {
            background: #e3e3e3;
        }

        .legend code .key {
            color: #ec433c;
        }

        .legend code .click {
            color: #2a48ec;
        }

        .top {
            border: 6px solid #e3e3e3;
            background-color: #e3e3e3;
        }

        #scoreboard {
            display: flex;
            padding-bottom: 12px;
            justify-content: space-between;
        }

        #scoreboard .select-wrap {
            font-weight: 700;
            vertical-align: top;
            display: inline-block;
        }

        #scoreboard .select-wrap select {
            margin: 0;
            height: 36px;
            border-radius: 0;
            border-width: 3px;
            border-style: solid;
            background-color: #d1d1d1;
            border-color: white #9e9e9e #9e9e9e white;
        }

        #scoreboard .select-wrap select:hover,
        #scoreboard .select-wrap select:focus {
            backgroudn-color: #ebebeb;
        }

        #scoreboard .reset {
            padding: 0 4px;
            font-size: 24px;
            cursor: pointer;
            font-weight: 700;
            line-height: 30px;
            border-width: 3px;
            border-style: solid;
            background-color: #d1d1d1;
            border-color: white #9e9e9e #9e9e9e white;
        }

        #scoreboard .reset:hover,
        #scoreboard .reset:focus {
            outline: none;
            background-color: #ebebeb;
        }

        #scoreboard .counter {
            padding: 0 4px;
            color: #ec433c;
            border: 3px inset;
            line-height: 30px;
            letter-spacing: 0.08em;
            display: inline-block;
            background: #333;
            text-shadow: 0 0 2px #ec433c;
        }

        #scoreboard .counter:first-of-type {
            margin-right: 20px;
        }

        #scoreboard .counter:last-of-type {
            margin-left: 20px;
        }

        #grid {
            margin: 0 auto;
            position: relative;
            display: inline-block;
        }

        #grid::-moz-selection {
            background-color: transparent;
        }

        #grid::selection {
            background-color: transparent;
        }

        #grid.disabled .cell {
            pointer-events: none;
        }

        #grid.disabled .status-indicator {
            top: 50%;
            left: 50%;
            z-index: 11;
            width: 8vw;
            height: 8vw;
            font-size: 4vw;
            cursor: pointer;
            line-height: 8vw;
            position: absolute;
            border-radius: 50%;
            pointer-events: auto;
            background-color: #d1d1d1;
            transform: translate(-50%, -50%);
            border: 1px solid rgba(51, 51, 51, 0.25);
        }

        #grid.disabled .status-indicator::after {
            content: '';
        }

        #grid.disabled.win .status-indicator::after {
            content: '😎';
        }

        #grid.disabled.lose .status-indicator::after {
            content: '☹️';
        }

        #grid .row {
            display: flex;
        }

        #grid .cell {
            cursor: pointer;
            width: 24px;
            height: 24px;
            position: relative;
            background-color: #d1d1d1;
            border-width: 3px;
            border-style: solid;
            border-color: white #9e9e9e #9e9e9e white;
        }

        #grid .cell i {
            left: 0;
            bottom: 0;
            margin: 0;
            width: 100%;
            font-size: 14px;
            font-weight: 700;
            font-style: normal;
            position: absolute;
            line-height: 24px;
        }

        #grid .cell::before {
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 1;
            content: '';
            position: absolute;
            background-color: #d1d1d1;
        }

        #grid .cell::after {
            top: 50%;
            left: 50%;
            content: '';
            position: absolute;
            transform: translate(-50%, -50%);
        }

        #grid .cell:hover::before {
            background-color: #ebebeb;
        }

        #grid .cell.revealed {
            border: 1px solid #b8b8b8;
        }

        #grid .cell.revealed::before {
            display: none;
        }

        #grid .cell.revealed .flag {
            display: none;
        }

        #grid .cell .flag {
            top: 50%;
            left: 50%;
            z-index: 1;
            width: 10px;
            height: 4px;
            position: absolute;
            border-style: solid;
            border-width: 4px 0 4px 10px;
            transform: translate(-50%, -50%);
            border-color: transparent transparent transparent #ec433c;
        }

        #grid .cell.maybe .flag {
            border-color: transparent transparent transparent #d7af42;
        }

        #grid .cell.mine {
            background-color: #ec433c;
        }

        #grid .cell.mine::after {
            border-radius: 50%;
            width: 12px;
            height: 12px;
            background-color: #333;
        }

        #grid .cell.incorrect .flag::before,
        #grid .cell.incorrect .flag::after {
            top: 50%;
            z-index: 1;
            left: -13px;
            height: 2px;
            width: 16px;
            content: '';
            position: absolute;
            background-color: black;
        }

        #grid .cell.incorrect .flag::before {
            transform: rotate(-45deg);
        }

        #grid .cell.incorrect .flag::after {
            transform: rotate(45deg);
        }

        #grid .cell.mousedown {
            border: none;
        }

        #leaderboard {
            margin-top: 20px;
            text-align: center;
        }

        #leaderboard h4 {
            margin: 0 0 10px;
        }

        #leaderboard ul {
            margin: 0;
            padding: 10px;
            display: inline-block;
            background-color: rgba(209, 209, 209, 0.5);
        }

        #leaderboard ul li {
            padding: 2px;
            list-style: none;
        }

        #leaderboard ul li span {
            font-weight: 900;
            text-transform: capitalize;
        }

        #leaderboard ul li.highlight {
            background-color: #fef178;
        }

        #leaderboard button {
            border: none;
            outline: none;
            cursor: pointer;
            font-size: 12px;
            font-weight: 700;
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            background: none;
            text-decoration: underline;
        }

        .cell.white {
            color: #fff;
        }

        .cell.gray-light {
            color: #efefef;
        }

        .cell.gray-mid {
            color: #e3e3e3;
        }

        .cell.gray {
            color: #d1d1d1;
        }

        .cell.gray-dark {
            color: #808080;
        }

        .cell.black {
            color: #333;
        }

        .cell.red {
            color: #ec433c;
        }

        .cell.maroon {
            color: #a6070f;
        }

        .cell.purple {
            color: #a42887;
        }

        .cell.yellow {
            color: #d7af42;
        }

        .cell.yellow-light {
            color: #fef178;
        }

        .cell.blue {
            color: #2a48ec;
        }

        .cell.blue-dark {
            color: #233db7;
        }

        .cell.green {
            color: #2bb13d;
        }

        .cell.turquoise {
            color: #28907d;
        }

        #devbox {
            top: 10px;
            right: 10px;
            position: fixed;
            text-align: left;
            max-width: 300px;
            color: #fff;
            background-color: #333;
        }

        #devbox p {
            margin: 0;
            font-size: 12px;
            padding: 10px 20px;
        }

        #devbox p+p {
            padding-top: 5px;
        }
    </style>
</head>

<body>
    <header class="site-header" role="banner">
        <div class="wrap">
            <h1>扫雷</h1>
            <div class="legend">
                <p><strong>插旗:</strong><code><span class="key">alt</span>+<span class="click">click</span></code></p>
            </div>
        </div>
    </header>
    <main class="site-main" role="main">
        <div class="wrap">
            <div id="board">
                <div class="top">
                    <div id="scoreboard">
                        <div id="minecounter" class="counter"></div>
                        <div>
                            <div class="select-wrap"><select name="level" id="level">
                                    <option value="beginner">初级</option>
                                    <option value="intermediate">中级</option>
                                    <option value="expert" selected>高级</option>
                                </select></div><button class="reset">🙂</button>
                        </div>
                        <div id="timer" class="counter"></div>
                    </div>
                    <div id="grid"></div>
                </div>
                <div class="bottom"></div>
            </div>
        </div>
    </main>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script>$(document).ready(function () { var $body, $document, $board, $grid, timer, time, unstarted; function _init() { $document = $(document); $body = $('body'); $body.addClass('loaded'); $board = $('#board'); $grid = $('#grid'); var $timer = $('#timer'); var $mineCounter = $('#minecounter'); var $levelSelect = $('#level'); var levels = { beginner: '9x9x10', intermediate: '16x16x44', expert: '16x30x99', }; var level = $levelSelect.val(); var levelParams, rows, $rows, columns, cellCount, mines, freeCells, mineTally, pauseTime, beginnerHighScore = 999, intermediateHighScore = 999, expertHighScore = 999; var countColors = { 0: '', 1: 'blue', 2: 'green', 3: 'red', 4: 'blue-dark', 5: 'maroon', 6: 'turquoise', 7: 'purple', 8: 'gray-dark', }; time = 0; timer = false; unstarted = true; var statusIndicator = '<div class="status-indicator"></div>'; function setLevel(level) { levelParams = levels[level]; rows = parseInt(levelParams.split('x')[0]); columns = parseInt(levelParams.split('x')[1]); cellCount = rows * columns; mines = levelParams.split('x')[2]; freeCells = cellCount - mines } function setBoard(level) { $grid.html(statusIndicator).removeClass('disabled lose win').addClass('unstarted'); setLevel(level); unstarted = true; for (r = 0; r < rows; r++) { var newCells = ''; for (c = 0; c < columns; c++) { newCells += '<div class="cell"></div>' } $grid.append('<div class="row">' + newCells + '</div>') } mineTally = mines; $mineCounter.html(mineTally); resetTimer() } setBoard(level); $('html').on('mousedown', '.reset', function () { $(this).text('😮') }).on('mouseup', '.reset', function () { $(this).text('🙂'); stopTimer(); level = $levelSelect.val(); setBoard(level) }); $('html').on('click', '.status-indicator', function () { level = $levelSelect.val(); setBoard(level) }); $levelSelect.on('change', function () { stopTimer(); resetTimer(); level = $levelSelect.val(); setBoard(level) }); function layMines(level, clickedCellIndex) { $rows = $('.row'); var freeCells = $('.cell'); var takenCells = [clickedCellIndex]; for (m = 0; m < mines; m++) { var mineCell = Math.floor(Math.random() * Math.floor(freeCells.length)); if ($.inArray(mineCell, takenCells) > -1) { m--; continue } takenCells.push(mineCell); $(freeCells[mineCell]).addClass('mine') } var $cells = $('.cell'); for (c = 0; c < $cells.length; c++) { var $cell = $($cells[c]); $cell.attr('data-cell', c); if ($cell.is('.mine')) { continue } var mineCount = 0; var rowPos = Math.floor(c / columns); var $currentRow = $cell.closest('.row'); $currentRow.attr('data-row', rowPos); var rowCells = $currentRow.find('.cell'); var cellPos = c % columns; if ($(rowCells[cellPos - 1]).is('.mine')) { mineCount++ } if ($(rowCells[cellPos + 1]).is('.mine')) { mineCount++ } if (rowPos > 0) { var prevRowCells = $($rows[rowPos - 1]).find('.cell'); if ($(prevRowCells[cellPos - 1]).is('.mine')) { mineCount++ } if ($(prevRowCells[cellPos]).is('.mine')) { mineCount++ } if ($(prevRowCells[cellPos + 1]).is('.mine')) { mineCount++ } } if (rowPos < rows - 1) { var nextRowCells = $($rows[rowPos + 1]).find('.cell'); if ($(nextRowCells[cellPos - 1]).is('.mine')) { mineCount++ } if ($(nextRowCells[cellPos]).is('.mine')) { mineCount++ } if ($(nextRowCells[cellPos + 1]).is('.mine')) { mineCount++ } } if (mineCount > 0) { $cell.html('<i>' + mineCount + '</i>'); var colorClass = countColors[mineCount]; $cell.addClass(colorClass) } else { $cell.addClass('zero') } } } $('html').off('click', '#grid.unstarted .cell').on('click', '#grid.unstarted .cell', function (e) { $grid.removeClass('unstarted'); if (unstarted && !$(e.target).is('.mine')) { layMines(level, $('.cell').index(this)); timer = window.setInterval(startTimer, 1000); unstarted = false } }); function resetTimer() { $timer.html('000'); time = 0 } function startTimer() { time++; if (time < 10) { $timer.html('00' + time) } else if (time > 9 && time < 100) { $timer.html('0' + time) } else { $timer.html(time) } } function stopTimer() { window.clearInterval(timer) } function pauseTimer() { stopTimer(); pauseTime = parseInt($('#timer').html()) } function unpauseTimer() { time = pauseTime; timer = window.setInterval(startTimer, 1000); pauseTime = false } $(window).on('blur', function () { pauseTimer() }).on('focus', function () { if (pauseTime) { unpauseTimer() } }); function checkCell($cell) { if (!$cell.is('.mine') && !$cell.is('.revealed')) { cellClick($cell, 'reveal'); if ($cell.is('.zero')) { $cell.trigger('click') } } } function cellClick($cell, action) { if (action === 'flag' && !$cell.is('.revealed')) { if ($cell.is('.flagged')) { $cell.removeClass('flagged'); $cell.addClass('maybe'); mineTally++; updateMinecounter(mineTally) } else if ($cell.is('.maybe')) { $cell.removeClass('maybe'); var flag = $cell.find('.flag'); flag.remove() } else { $cell.addClass('flagged'); $cell.append('<span class="flag"></span>'); mineTally--; updateMinecounter(mineTally) } } else if (action === 'reveal') { $cell.addClass('revealed'); if ($cell.is('.mine')) { lose() } statusCheck() } else if (action === 'clear') { if (!$cell.is('.revealed') || $cell.is('.zero')) { return } clearClick($cell) } } function updateMinecounter(mineTally) { if (mineTally < 10) { $mineCounter.html('0' + mineTally) } else { $mineCounter.html(mineTally) } } function zeroClick($cell) { var cellPos = $cell.prevAll().length; var $currentRow = $cell.closest('.row'); var rowPos = parseInt($currentRow.attr('data-row')); var rowCells = $currentRow.find('.cell'); checkCell($(rowCells[cellPos - 1])); checkCell($(rowCells[cellPos + 1])); if (rowPos > 0) { var prevRowCells = $($rows[rowPos - 1]).find('.cell'); checkCell($(prevRowCells[cellPos - 1])); checkCell($(prevRowCells[cellPos])); checkCell($(prevRowCells[cellPos + 1])) } if (rowPos < rows) { var nextRowCells = $($rows[rowPos + 1]).find('.cell'); checkCell($(nextRowCells[cellPos - 1])); checkCell($(nextRowCells[cellPos])); checkCell($(nextRowCells[cellPos + 1])) } } function clearClick($cell) { var cellPos = $cell.prevAll().length; var $currentRow = $cell.closest('.row'); var rowPos = parseInt($currentRow.attr('data-row')); var rowCells = $currentRow.find('.cell'); var adjacentCells = []; var correctClear = true; var adjacentMines = 0; var adjacentFlags = 0; var i; adjacentCells.push($(rowCells[cellPos - 1])); adjacentCells.push($(rowCells[cellPos + 1])); if (rowPos > 0) { var prevRowCells = $($rows[rowPos - 1]).find('.cell'); adjacentCells.push($(prevRowCells[cellPos - 1])); adjacentCells.push($(prevRowCells[cellPos])); adjacentCells.push($(prevRowCells[cellPos + 1])) } if (rowPos < rows) { var nextRowCells = $($rows[rowPos + 1]).find('.cell'); adjacentCells.push($(nextRowCells[cellPos - 1])); adjacentCells.push($(nextRowCells[cellPos])); adjacentCells.push($(nextRowCells[cellPos + 1])) } for (i = 0; i < adjacentCells.length; i++) { if ($(adjacentCells[i]).is('.mine')) { adjacentMines++ } if ($(adjacentCells[i]).is('.flagged')) { adjacentFlags++ } } if (adjacentFlags === adjacentMines) { for (i = 0; i < adjacentCells.length; i++) { if ($(adjacentCells[i]).is('.mine')) { if ($(adjacentCells[i]).is('.flagged')) { continue } else { $(adjacentCells[i]).addClass('revealed'); correctClear = false } } else if ($(adjacentCells[i]).is('.flagged')) { correctClear = false; $(adjacentCells[i]).addClass('incorrect'); lose() } } if (correctClear) { for (i = 0; i < adjacentCells.length; i++) { if (!$(adjacentCells[i]).is('.mine')) { if ($(adjacentCells[i]).is('.zero')) { zeroClick($(adjacentCells[i])) } cellClick($(adjacentCells[i]), 'reveal') } } } } else { return } } function statusCheck() { if ($('.cell.revealed').length == freeCells) { stopTimer(); var winTime = $('#timer').html(); $grid.addClass('disabled win'); resetHighScore(level, winTime) } } function lose() { $grid.addClass('disabled lose'); stopTimer() } $('html').on('click', '.cell', function (e) { e.preventDefault(); var action = 'reveal'; var $cell = $(this); if (e.altKey || e.which === 3) { action = 'flag' } else if ($cell.is('.revealed') || (e.which === 1) & (e.which === 3)) { action = 'clear' } if ($cell.is('.flagged') && !e.altKey) { return } if ($cell.is('.zero')) { zeroClick($cell) } cellClick($cell, action) }); $('html').on('mousedown', '.cell:not(.revealed,.flagged)', function (e) { if (!e.altKey && e.which !== 3) { $(this).addClass('mousedown') } }).on('mouseup mouseleave', '.cell.mousedown', function () { $(this).removeClass('mousedown') }); function resetHighScore(level, winTime) { if (localStorage.getItem(level)) { if (winTime < localStorage.getItem(level)) { localStorage.setItem(level, winTime); populateHighScore(level, winTime, true) } } else { localStorage.setItem(level, winTime); populateHighScore(level, winTime, true) } } function populateHighScore(level, highScore, highlight) { if (!$('#leaderboard').length) { $board.find('.bottom').append('<div id="leaderboard"><h4>High Scores</h4><ul><li class="beginner"></li><li class="intermediate"></li><li class="expert"></li></ul><div><button id="score-reset" class="score-reset">Clear Scores</button></div></div>') } if (highlight === true) { $('#leaderboard .highlight:not(.' + level + ')').removeClass('highlight'); $('#leaderboard .' + level).addClass('highlight') } var highScoreDisplay = parseInt(highScore, 10); $('#leaderboard .' + level).html('<span>' + level + '</span>: ' + highScoreDisplay + ' seconds') } function clearScores() { localStorage.clear(); $('#leaderboard').remove() } $('html').on('click', '#score-reset', clearScores) } _init() });</script>
</body>

</html>